/*
 * @(#)SearchableBar.java 10/11/2005
 *
 * Copyright 2002 - 2005 JIDE Software Inc. All rights reserved.
 */
package com.jidesoft.swing;

import com.jidesoft.plaf.UIDefaultsLookup;
import com.jidesoft.popup.JidePopup;
import com.jidesoft.swing.event.SearchableEvent;
import com.jidesoft.swing.event.SearchableListener;

import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;

/**
 * <code>SearchableBar</code> 是一个实现了查找功能的便捷组件. 由于组件支持 <code>Searchable</code>
 * 功能, 它可以和 <code>SearchableBar</code> 一起使用.
 * <p/>
 * 不同于 <code>Searchable</code> 的功能, 使用一个小的弹出窗口允许用户输入查找的文本,
 * <code>SearchableBar</code> 提供了一个全尺寸面板. 尽管他们都提供了差不多一样的功能设置,
 * 他们需要在不同的场景中来实现最令人满意的结果.
 * <p/>
 * 首先, <code>SearchableBar</code> 比 <code>Searchable</code> 的弹出窗口稍大一点,
 * 在屏幕上需要更多的空间. 安装 <code>SearchableBar</code> 的组件需要足够大. 相比之下,
 * <code>Searchable</code> 可以安装在任意大小的组件上作为一个浮动的弹出窗口.
 * <p/>
 * 其次, <code>SearchableBar</code> 可以设置在任何时候可见或者通过一个快捷键设置可见, 一直保持可见除非用户明确隐藏它.
 * 如果你的用户不精通计算, <code>SearchableBar</code> 是非常适当的因为用户可以非常容易的查看查找功能.
 * <code>SearchableBar</code> 也是传统 "查找" 或者 "搜索" 对话框的一个很好的替代, 因为
 * <code>SearchableBar</code> 不像模式对话框会限制用户输入. 相比之下, <code>Searchable</code>
 * 的弹出窗口是临时的. 鼠标点击弹出窗口外部会隐藏窗口. 对于精通计算的人来说是非常有用的, 但是它可能对于非精确计算用户是很难理解的. 一个很好的例子就是
 * IntelliJ IDEA 大量使用 Searchable 弹出窗口, 因为用户都是 Java 开发者. 火狐, 另一方面, 用
 * SearchableBar, 因为用户都是常规计算者.
 * <p/>
 * 尽管外观方面, 这两个非常不同, 但是它们都基于 {@link Searchable} 接口. 因此对于开发者, 两者几乎是一样的.
 * <code>SearchableBar</code> 基于 <code>Searchable</code>. 因此如果你有一个
 * <code>Searchable</code> 接口, 你都需要去调用 <code><pre>
 * SearchableBar.install(searchable, KeyStroke.getKeyStroke(KeyEvent.VK_F,
 * KeyEvent.CTRL_DOWN_MASK),
 * new SearchableBar.Installer() {
 *     public void openSearchBar(SearchableBar searchableBar) {
 *        // add code to show search bar
 *     }
 * <p/>
 *     public void closeSearchBar(SearchableBar searchableBar) {
 *        // add code to close search bar
 * }
 * });
 * </pre></code> 或者你想完全控制 SearchableBar, 你可以使用它的任一构造方法创建一个对象并添加到你想添加的地方.
 * <p/>
 * <code>SearchableBar</code> 有一些选项可以设置. 你可以设置紧实模式或者完全模式. 紧实模式仅仅显示按钮的图标,
 * 完全模式同时使用按钮的图标和文本. <code>SearchableBar</code> 上所有的按钮都可以通过
 * {@link #setVisibleButtons(int)} 方法来显示和隐藏. 你也可以设置未匹配文本区域的背景通过使用
 * {@link #setMismatchForeground(java.awt.Color)}.
 * <p/>
 */
public class SearchableBar extends JToolBar implements SearchableProvider {
	/**
	 * 
	 */
	private static final long serialVersionUID = 5340701591685292472L;

	private Searchable _searchable;

	protected JLabel _statusLabel;
	protected JLabel _leadingLabel;
	protected JTextField _textField;
	protected JComboBox _comboBox;

	protected AbstractButton _closeButton;
	protected AbstractButton _findPrevButton;
	protected AbstractButton _findNextButton;
	protected AbstractButton _highlightsButton;

	protected AbstractButton _matchCaseCheckBox;
	protected AbstractButton _wholeWordsCheckBox;
	protected AbstractButton _repeatCheckBox;

	public static final int SHOW_CLOSE = 0x1;
	public static final int SHOW_NAVIGATION = 0x2;
	public static final int SHOW_HIGHLIGHTS = 0x4;
	public static final int SHOW_MATCHCASE = 0x8;
	public static final int SHOW_REPEATS = 0x10;
	public static final int SHOW_STATUS = 0x20;
	public static final int SHOW_WHOLE_WORDS = 0x40;
	public static final int SHOW_ALL = 0xFFFFFFFF;

	public static final String PROPERTY_MAX_HISTORY_LENGTH = "maxHistoryLength";

	private int _visibleButtons = ~SHOW_REPEATS; // default is show all but
													// repeats
	private boolean _compact;
	private boolean _showMatchCount = false;

	private JidePopup _messagePopup;
	private MouseMotionListener _mouseMotionListener;
	private KeyListener _keyListener;
	private List<String> _searchHistory;
	private int _maxHistoryLength = 0;

	/**
	 * 创建一个可检索的工具栏.
	 *
	 * @param searchable
	 *            可检索的工具栏
	 */
	public SearchableBar(Searchable searchable) {
		this(searchable, "", false);
	}

	/**
	 * 创建一个紧实模式或者全模式下的可检索的工具栏.
	 *
	 * @param searchable
	 *            可检索的工具栏
	 * @param compact
	 *            紧实模式或者全模式的标志
	 */
	public SearchableBar(Searchable searchable, boolean compact) {
		this(searchable, "", compact);
	}

	/**
	 * 用初始文本创建一个紧实模式或者全模式下的可检索的工具栏.
	 *
	 * @param searchable
	 *            可检索的工具栏
	 * @param initialText
	 *            初始查找文本
	 * @param compact
	 *            紧实模式或者全模式的标志
	 */
	public SearchableBar(Searchable searchable, String initialText, boolean compact) {
		setFloatable(false);
		setRollover(true);
		_searchable = searchable;
		_searchable.addSearchableListener(new SearchableListener() {
			public void searchableEventFired(SearchableEvent e) {
				if (e.getID() == SearchableEvent.SEARCHABLE_MODEL_CHANGE && _searchable.getSearchingText() != null
						&& _searchable.getSearchingText().length() != 0) {
					highlightAllOrNext();
				}
			}
		});
		_searchable.setSearchableProvider(this);
		_compact = compact;
		initComponents(initialText);
	}

	private void initComponents(String initialText) {
		final AbstractAction closeAction = new AbstractAction() {
			private static final long serialVersionUID = -2245391247321137224L;

			public void actionPerformed(ActionEvent e) {
				if (getInstaller() != null) {
					getInstaller().closeSearchBar(SearchableBar.this);
				}
			}
		};

		final AbstractAction findNextAction = new AbstractAction() {
			private static final long serialVersionUID = -5263488798121831276L;

			public void actionPerformed(ActionEvent e) {
				_highlightsButton.setSelected(false);
				String text = getSearchingText();
				addSearchingTextToHistory(text);
				int cursor = _searchable.getSelectedIndex();
				_searchable.setCursor(cursor);
				int found = _searchable.findNext(text);
				if (found == cursor) {
					select(found, text, false);
					clearStatus();
				} else if (found != -1 && _searchable.isRepeats() && found <= cursor) {
					select(found, text, false);
					setStatus(getResourceString("SearchableBar.reachedBottomRepeat"),
							getImageIcon(SearchableBarIconsFactory.Buttons.REPEAT));
				} else if (!_searchable.isRepeats() && found == -1) {
					setStatus(getResourceString("SearchableBar.reachedBottom"),
							getImageIcon(SearchableBarIconsFactory.Buttons.ERROR));
				} else if (found != -1) {
					select(found, text, false);
					clearStatus();
					if (_searchable.getSearchingDelay() < 0) { // never updated
																// the count so
																// we update
																// here
						highlightAllOrNext();
					}
				}
			}
		};

		final AbstractAction findPrevAction = new AbstractAction() {
			private static final long serialVersionUID = -2534332227053620232L;

			public void actionPerformed(ActionEvent e) {
				_highlightsButton.setSelected(false);
				String text = getSearchingText();
				addSearchingTextToHistory(text);
				int cursor = _searchable.getSelectedIndex();
				_searchable.setCursor(cursor);
				int found = _searchable.findPrevious(text);
				if (found == cursor) {
					select(found, text, false);
					clearStatus();
				} else if (found != -1 && _searchable.isRepeats() && found >= cursor) {
					select(found, text, false);
					setStatus(getResourceString("SearchableBar.reachedTopRepeat"),
							getImageIcon(SearchableBarIconsFactory.Buttons.REPEAT));
				} else if (!_searchable.isRepeats() && found == -1) {
					setStatus(getResourceString("SearchableBar.reachedTop"),
							getImageIcon(SearchableBarIconsFactory.Buttons.ERROR));
				} else if (found != -1) {
					select(found, text, false);
					clearStatus();
				}
			}
		};

		_mouseMotionListener = new MouseMotionListener() {
			public void mouseMoved(MouseEvent e) {
				hideMessage();
			}

			public void mouseDragged(MouseEvent e) {

			}
		};
		_keyListener = new KeyListener() {
			public void keyTyped(KeyEvent e) {
				hideMessage();
			}

			public void keyPressed(KeyEvent e) {

			}

			public void keyReleased(KeyEvent e) {

			}
		};

		_closeButton = createCloseButton(closeAction);
		_findNextButton = createFindNextButton(findNextAction);
		_findPrevButton = createFindPrevButton(findPrevAction);
		_highlightsButton = createHighlightButton();
		_matchCaseCheckBox = createMatchCaseButton();
		_wholeWordsCheckBox = createWholeWordsButton();
		_repeatCheckBox = createRepeatsButton();

		_statusLabel = new JLabel();

		// setup text field
		_textField = createTextField();
		_textField.addFocusListener(new FocusAdapter() {
			@Override
			public void focusGained(FocusEvent e) {
				_textField.selectAll();
			}
		});
		_textField.setColumns(13);
		DocumentListener listener = new DocumentListener() {
			private Timer timer = new Timer(_searchable.getSearchingDelay(), new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					highlightAllOrNext();
				}
			});

			public void insertUpdate(DocumentEvent e) {
				startTimer();
			}

			public void removeUpdate(DocumentEvent e) {
				startTimer();
			}

			public void changedUpdate(DocumentEvent e) {
				startTimer();
			}

			void startTimer() {
				if (_searchable.getSearchingDelay() > 0) {
					if (timer.isRunning()) {
						timer.restart();
					} else {
						timer.setRepeats(false);
						timer.start();
					}
				} else if (_searchable.getSearchingDelay() == 0) {
					highlightAllOrNext();
				}
			}
		};
		_textField.getDocument().addDocumentListener(listener);
		_textField.setText(initialText);

		_textField.registerKeyboardAction(findNextAction, KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0),
				JComponent.WHEN_FOCUSED);
		_textField.registerKeyboardAction(findNextAction, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0),
				JComponent.WHEN_FOCUSED);
		_textField.registerKeyboardAction(findPrevAction, KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0),
				JComponent.WHEN_FOCUSED);
		_textField.registerKeyboardAction(closeAction, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
				JComponent.WHEN_FOCUSED);

		_comboBox = createComboBox();
		if (_comboBox.getEditor().getEditorComponent() instanceof JTextField) {
			((JTextField) _comboBox.getEditor().getEditorComponent()).getDocument().addDocumentListener(listener);
			registerKeyboardActions(closeAction, findNextAction, findPrevAction);
			_comboBox.addPopupMenuListener(new PopupMenuListener() {
				@Override
				public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
					unregisterKeyboardActions();
				}

				@Override
				public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
					registerKeyboardActions(closeAction, findNextAction, findPrevAction);
				}

				@Override
				public void popupMenuCanceled(PopupMenuEvent e) {
					registerKeyboardActions(closeAction, findNextAction, findPrevAction);
				}
			});
		}
		_comboBox.setSelectedItem(initialText);
		_comboBox.setPreferredSize(_textField.getPreferredSize());

		installComponents();

		int found = _searchable.findFromCursor(getSearchingText());
		if (initialText.length() != 0 && found == -1) {
			select(found, initialText, false);
		}
	}

	private void registerKeyboardActions(AbstractAction closeAction, AbstractAction findNextAction,
			AbstractAction findPrevAction) {
		((JTextField) _comboBox.getEditor().getEditorComponent()).registerKeyboardAction(findNextAction,
				KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), JComponent.WHEN_FOCUSED);
		((JTextField) _comboBox.getEditor().getEditorComponent()).registerKeyboardAction(findNextAction,
				KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), JComponent.WHEN_FOCUSED);
		((JTextField) _comboBox.getEditor().getEditorComponent()).registerKeyboardAction(findPrevAction,
				KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), JComponent.WHEN_FOCUSED);
		((JTextField) _comboBox.getEditor().getEditorComponent()).registerKeyboardAction(closeAction,
				KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_FOCUSED);
	}

	private void unregisterKeyboardActions() {
		((JTextField) _comboBox.getEditor().getEditorComponent())
				.unregisterKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0));
		((JTextField) _comboBox.getEditor().getEditorComponent())
				.unregisterKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0));
		((JTextField) _comboBox.getEditor().getEditorComponent())
				.unregisterKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0));
		((JTextField) _comboBox.getEditor().getEditorComponent())
				.unregisterKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0));
	}

	/**
	 * 创建一个用户输入查找文本的文本域.
	 *
	 * @return 文本域.
	 */
	protected JTextField createTextField() {
		return new JTextField();
	}

	/**
	 * 创建一个用户输入查找文本的组合框.
	 *
	 * @return 组合框.
	 * @since 3.4.1
	 */
	protected JComboBox createComboBox() {
		JComboBox comboBox = new JComboBox();
		comboBox.setEditable(true);
		return comboBox;
	}

	/**
	 * 获取基础的 Searchable 对象.
	 *
	 * @return Searchable 对象.
	 */
	public Searchable getSearchable() {
		return _searchable;
	}

	/**
	 * 创建关闭按钮. 子类可以重写创建自己的关闭按钮.
	 *
	 * @param closeAction
	 *            关闭行为
	 *
	 * @return 关闭按钮.
	 */
	protected AbstractButton createCloseButton(AbstractAction closeAction) {
		AbstractButton button = new JButton(getImageIcon(SearchableBarIconsFactory.Buttons.CLOSE));
		button.addActionListener(closeAction);
		button.setRolloverEnabled(true);
		button.setBorder(BorderFactory.createEmptyBorder());
		button.setOpaque(false);
		button.setRequestFocusEnabled(false);
		button.setFocusable(false);
		button.setRolloverIcon(getImageIcon(SearchableBarIconsFactory.Buttons.CLOSE_ROLLOVER));
		return button;
	}

	/**
	 * 创建查找下一个按钮. 子类可以重写创建自己的查找下一个按钮.
	 *
	 * @param findNextAction
	 *            查找下一个按钮行为
	 *
	 * @return 查找下一个按钮.
	 */
	protected AbstractButton createFindNextButton(AbstractAction findNextAction) {
		AbstractButton button = new JButton(_compact ? "" : getResourceString("SearchableBar.findNext"),
				getImageIcon(SearchableBarIconsFactory.Buttons.NEXT));
		button.setToolTipText(getResourceString("SearchableBar.findNext.tooltip"));
		button.setMnemonic(getResourceString("SearchableBar.findNext.mnemonic").charAt(0));
		button.setRolloverIcon(getImageIcon(SearchableBarIconsFactory.Buttons.NEXT_ROLLOVER));
		button.setDisabledIcon(getImageIcon(SearchableBarIconsFactory.Buttons.NEXT_DISABLED));
		button.setRequestFocusEnabled(false);
		button.setFocusable(false);
		button.addActionListener(findNextAction);
		button.setEnabled(false);
		return button;
	}

	/**
	 * 创建查找上一个按钮. 子类可以重写创建自己的查找上一个按钮.
	 *
	 * @param findPrevAction
	 *            查找上一个按钮行为
	 *
	 * @return 查找上一个按钮.
	 */
	protected AbstractButton createFindPrevButton(AbstractAction findPrevAction) {
		AbstractButton button = new JButton(_compact ? "" : getResourceString("SearchableBar.findPrevious"),
				getImageIcon(SearchableBarIconsFactory.Buttons.PREVIOUS));
		button.setToolTipText(getResourceString("SearchableBar.findPrevious.tooltip"));
		button.setMnemonic(getResourceString("SearchableBar.findPrevious.mnemonic").charAt(0));
		button.setRolloverIcon(getImageIcon(SearchableBarIconsFactory.Buttons.PREVIOUS_ROLLOVER));
		button.setDisabledIcon(getImageIcon(SearchableBarIconsFactory.Buttons.PREVIOUS_DISABLED));
		button.setRequestFocusEnabled(false);
		button.setFocusable(false);
		button.addActionListener(findPrevAction);
		button.setEnabled(false);
		return button;
	}

	/**
	 * 创建高亮按钮.
	 *
	 * @return 高亮按钮.
	 */
	protected AbstractButton createHighlightButton() {
		AbstractButton button = new JToggleButton(_compact ? "" : getResourceString("SearchableBar.highlights"),
				getImageIcon(SearchableBarIconsFactory.Buttons.HIGHLIGHTS));
		button.setToolTipText(getResourceString("SearchableBar.highlights.tooltip"));
		button.setMnemonic(getResourceString("SearchableBar.highlights.mnemonic").charAt(0));
		button.setSelectedIcon(getImageIcon(SearchableBarIconsFactory.Buttons.HIGHLIGHTS_SELECTED));
		button.setDisabledIcon(getImageIcon(SearchableBarIconsFactory.Buttons.HIGHLIGHTS_DISABLED));
		button.setRolloverIcon(getImageIcon(SearchableBarIconsFactory.Buttons.HIGHLIGHTS_ROLLOVER));
		button.setRolloverSelectedIcon(getImageIcon(SearchableBarIconsFactory.Buttons.HIGHLIGHTS_ROLLOVER_SELECTED));
		button.setRequestFocusEnabled(false);
		button.setFocusable(false);

		AbstractAction highlightAllAction = new AbstractAction() {
			private static final long serialVersionUID = 5170786863522331175L;

			public void actionPerformed(ActionEvent e) {
				addSearchingTextToHistory(getSearchingText());
				highlightAllOrNext();
			}
		};

		button.addActionListener(highlightAllAction);
		button.setEnabled(false);
		return button;
	}

	/**
	 * Creates the repeat button. By default it will return a JCheckBox.
	 * Subclass class can override it to return your own button or customize the
	 * button created by default as long as it can set underlying Searchable's
	 * repeats property.
	 *
	 * @return the repeat button.
	 */
	protected AbstractButton createRepeatsButton() {
		AbstractButton button = new JCheckBox(getResourceString("SearchableBar.repeats"));
		button.setMnemonic(getResourceString("SearchableBar.repeats.mnemonic").charAt(0));
		button.setRequestFocusEnabled(false);
		button.setFocusable(false);
		button.setSelected(getSearchable().isRepeats());
		button.addItemListener(new ItemListener() {
			public void itemStateChanged(ItemEvent e) {
				if (e.getSource() instanceof AbstractButton) {
					getSearchable().setRepeats(((AbstractButton) e.getSource()).isSelected());
				}
			}
		});
		button.setOpaque(false);
		return button;
	}

	/**
	 * 创建区分大小写按钮. By default it will return a JCheckBox. Subclass class can
	 * override it to return your own button or customize the button created by
	 * default as long as it can set underlying Searchable's caseSensitive
	 * property.
	 *
	 * @return 区分大小写按钮.
	 */
	protected AbstractButton createMatchCaseButton() {
		JCheckBox checkBox = new JCheckBox(getResourceString("SearchableBar.matchCase"));
		checkBox.setMnemonic(getResourceString("SearchableBar.matchCase.mnemonic").charAt(0));
		checkBox.setRequestFocusEnabled(false);
		checkBox.setFocusable(false);
		checkBox.setSelected(getSearchable().isCaseSensitive());
		checkBox.addItemListener(new ItemListener() {
			public void itemStateChanged(ItemEvent e) {
				if (e.getSource() instanceof AbstractButton) {
					getSearchable().setCaseSensitive(((AbstractButton) e.getSource()).isSelected());
					addSearchingTextToHistory(getSearchingText());
					highlightAllOrNext();
				}
			}
		});
		checkBox.setOpaque(false);
		return checkBox;
	}

	/**
	 * Creates the whole words button. By default it will return a JCheckBox.
	 * Subclass class can override it to return your own button or customize the
	 * button created by default as long as it can set underlying Searchable's
	 * toEnd property.
	 *
	 * @return the whole words button.
	 * @since 3.5.2
	 */
	protected AbstractButton createWholeWordsButton() {
		JCheckBox checkBox = new JCheckBox(getResourceString("SearchableBar.wholeWords"));
		checkBox.setMnemonic(getResourceString("SearchableBar.wholeWords.mnemonic").charAt(0));
		checkBox.setRequestFocusEnabled(false);
		checkBox.setFocusable(false);
		if (getSearchable() instanceof WholeWordsSupport) {
			checkBox.setSelected(((WholeWordsSupport) getSearchable()).isWholeWords());
		} else {
			checkBox.setSelected(false);
			checkBox.setEnabled(false);
		}
		checkBox.addItemListener(new ItemListener() {
			public void itemStateChanged(ItemEvent e) {
				if (e.getSource() instanceof AbstractButton && getSearchable() instanceof WholeWordsSupport) {
					((WholeWordsSupport) getSearchable()).setWholeWords(((AbstractButton) e.getSource()).isSelected());
					addSearchingTextToHistory(getSearchingText());
					highlightAllOrNext();
				}
			}
		});
		checkBox.setOpaque(false);
		return checkBox;
	}

	/**
	 * Adds the buttons to the SearchableBar. Subclass can override this method
	 * to rearrange the layout of those buttons.
	 */
	protected void installComponents() {
		setBorder(BorderFactory.createEtchedBorder());
		setLayout(new JideBoxLayout(this, JideBoxLayout.X_AXIS));
		add(Box.createHorizontalStrut(4), JideBoxLayout.FIX);
		if ((_visibleButtons & SHOW_CLOSE) != 0) {
			add(_closeButton);
			add(Box.createHorizontalStrut(10));
		}
		// setup the label
		_leadingLabel = new JLabel(getResourceString("SearchableBar.find"));
		_leadingLabel.setDisplayedMnemonic(getResourceString("SearchableBar.find.mnemonic").charAt(0));
		add(_leadingLabel);
		add(Box.createHorizontalStrut(2), JideBoxLayout.FIX);
		add(JideSwingUtilities.createCenterPanel(_textField), JideBoxLayout.FIX);
		add(JideSwingUtilities.createCenterPanel(_comboBox), JideBoxLayout.FIX);
		if (getMaxHistoryLength() == 0) {
			_leadingLabel.setLabelFor(_textField);
			_textField.setVisible(true);
			_comboBox.setVisible(false);
		} else {
			_leadingLabel.setLabelFor(_comboBox);
			_comboBox.setVisible(true);
			_textField.setVisible(false);
		}
		add(Box.createHorizontalStrut(2), JideBoxLayout.FIX);
		if ((_visibleButtons & SHOW_NAVIGATION) != 0) {
			add(_findNextButton);
			add(_findPrevButton);
		}
		if ((_visibleButtons & SHOW_HIGHLIGHTS) != 0) {
			add(_highlightsButton);
		}
		if ((_visibleButtons & SHOW_MATCHCASE) != 0) {
			add(_matchCaseCheckBox);
			add(Box.createHorizontalStrut(2));
		}
		if ((_visibleButtons & SHOW_WHOLE_WORDS) != 0 && getSearchable() instanceof WholeWordsSupport) {
			add(_wholeWordsCheckBox);
			add(Box.createHorizontalStrut(2));
		}
		if ((_visibleButtons & SHOW_REPEATS) != 0) {
			add(_repeatCheckBox);
			add(Box.createHorizontalStrut(2));
		}
		if ((_visibleButtons & SHOW_STATUS) != 0) {
			add(Box.createHorizontalStrut(24));
			add(_statusLabel, JideBoxLayout.VARY);
		}
		add(Box.createHorizontalStrut(6), JideBoxLayout.FIX);
	}

	/**
	 * Get if the SearchableBar is highlighting all matches.
	 * <p/>
	 * Even you set it to true, after the customer press previous or next
	 * button, this flag will be cleared.
	 *
	 * @return true if all matches are highlighted. Otherwise false.
	 */
	public boolean isHighlightAll() {
		return _highlightsButton.isSelected();
	}

	/**
	 * Set if the SearchableBar will highlight all matches.
	 *
	 * @param highlightAll
	 *            the flag
	 *
	 * @see #isHighlightAll()
	 */
	public void setHighlightAll(boolean highlightAll) {
		_highlightsButton.setSelected(highlightAll);
	}

	private int _previousCursor = -1;

	private void highlightAllOrNext() {
		if (_highlightsButton.isSelected()) {
			_previousCursor = _searchable.getCurrentIndex();
			highlightNext();
			highlightAll();
		} else {
			if (_previousCursor >= 0) {
				_searchable.setCursor(_previousCursor);
				_searchable.setSelectedIndex(_previousCursor, false);
			}
			highlightNext();
		}
	}

	private void highlightAll() {
		String text = getSearchingText();
		if (text == null || text.length() == 0) {
			_findNextButton.setEnabled(false);
			_findPrevButton.setEnabled(false);
			_highlightsButton.setEnabled(false);
			select(-1, "", false);
			clearStatus();
			return;
		}
		boolean old = _searchable.isRepeats();
		_searchable.setRepeats(false);
		int index = _searchable.findFirst(text);
		if (index != -1) {
			_searchable.setSelectedIndex(index, false); // clear side effect of
														// ctrl-a will select
														// all items
			_searchable.setCursor(index); // as setSelectedIndex is used
											// directly, we have to manually set
											// the cursor value.
			_findNextButton.setEnabled(true);
			_findPrevButton.setEnabled(true);
			_highlightsButton.setEnabled(true);
			clearStatus();
		} else {
			select(-1, text, false);
			_findNextButton.setEnabled(false);
			_findPrevButton.setEnabled(false);
			_highlightsButton.setEnabled(false);
			setStatus(getResourceString("SearchableBar.notFound"),
					getImageIcon(SearchableBarIconsFactory.Buttons.ERROR));
		}

		_searchable.highlightAll();
		_searchable.setRepeats(old);
		_searchable.setCursor(0);
	}

	private void highlightNext() {
		_searchable.cancelHighlightAll();
		String text = getSearchingText();
		if (text == null || text.length() == 0) {
			_findNextButton.setEnabled(false);
			_findPrevButton.setEnabled(false);
			_highlightsButton.setEnabled(false);
			select(-1, "", false);
			clearStatus();
			return;
		}
		int found = _searchable.findFromCursor(text);
		if (found == -1) {
			select(-1, "", false);
			_findNextButton.setEnabled(false);
			_findPrevButton.setEnabled(false);
			_highlightsButton.setEnabled(false);
			setStatus(getResourceString("SearchableBar.notFound"),
					getImageIcon(SearchableBarIconsFactory.Buttons.ERROR));
		} else {
			select(found, text, false);
			_findNextButton.setEnabled(true);
			_findPrevButton.setEnabled(true);
			_highlightsButton.setEnabled(true);
			clearStatus();
		}
	}

	private void clearStatus() {
		_statusLabel.setIcon(null);
		_textField.setBackground(UIDefaultsLookup.getColor("TextField.background"));
		_comboBox.getEditor().getEditorComponent().setBackground(UIDefaultsLookup.getColor("TextField.background"));
		if (isShowMatchCount() && (_textField.getText().length() > 0
				|| (_comboBox.isVisible() && _comboBox.getEditor().getEditorComponent() instanceof JTextField
						&& ((JTextField) _comboBox.getEditor().getEditorComponent()).getText().length() > 0))) {
			_statusLabel.setText(getSearchable().getMatchCount() + " " + getResourceString("SearchableBar.matches"));
		} else {
			_statusLabel.setText("");
		}
		hideMessage();
	}

	private void setStatus(String message, Icon icon) {
		_statusLabel.setIcon(icon);
		_statusLabel.setText(message);
		_statusLabel.setToolTipText(message);
		if (!_statusLabel.isShowing() || _statusLabel.getWidth() < 25) {
			showMessage(message);
		}
	}

	/**
	 * Makes the search field having focus.
	 */
	public void focusSearchField() {
		if (_textField != null && _textField.isVisible()) {
			_textField.requestFocus();
		}
		if (_comboBox != null && _comboBox.isVisible()) {
			_comboBox.requestFocus();
		}
	}

	protected void select(int index, String searchingText, boolean incremental) {
		if (index != -1) {
			_searchable.setSelectedIndex(index, incremental);
			_searchable.setCursor(index, incremental);
			_textField.setBackground(UIDefaultsLookup.getColor("TextField.background"));
			_comboBox.getEditor().getEditorComponent().setBackground(UIDefaultsLookup.getColor("TextField.background"));
		} else {
			_searchable.setSelectedIndex(-1, false);
			_textField.setBackground(getMismatchBackground());
			_comboBox.getEditor().getEditorComponent().setBackground(UIDefaultsLookup.getColor("TextField.background"));
		}
		_searchable.firePropertyChangeEvent(searchingText);
		if (index != -1) {
			Object element = _searchable.getElementAt(index);
			_searchable.fireSearchableEvent(new SearchableEvent(_searchable, SearchableEvent.SEARCHABLE_MATCH,
					searchingText, element, _searchable.convertElementToString(element)));
		} else {
			_searchable.fireSearchableEvent(
					new SearchableEvent(_searchable, SearchableEvent.SEARCHABLE_NOMATCH, searchingText));
		}
	}

	/**
	 * Gets the searching text.
	 *
	 * @return the searching text.
	 */
	public String getSearchingText() {
		if (_textField != null && _textField.isVisible()) {
			return _textField.getText();
		}
		if (_comboBox != null && _comboBox.isVisible()) {
			Object item = _comboBox.getEditor().getItem();
			return item == null ? "" : item.toString();
		}
		return "";
	}

	/**
	 * 设置搜索文本.
	 *
	 * @param searchingText
	 *            新的搜索文本.
	 */
	public void setSearchingText(String searchingText) {
		if (_textField != null && _textField.isVisible()) {
			_textField.setText(searchingText);
		}
		if (_comboBox != null && _comboBox.isVisible()) {
			_comboBox.setSelectedItem(searchingText);
		}
	}

	/**
	 * Returns false.
	 *
	 * @return false.
	 */
	public boolean isPassive() {
		return false;
	}

	final private static Color DEFAULT_MISMATCH_BACKGROUND = new Color(255, 85, 85);
	private Color _mismatchBackground;

	/**
	 * Sets the background for mismatch.
	 *
	 * @param mismatchBackground
	 *            the mismatch background
	 */
	public void setMismatchForeground(Color mismatchBackground) {
		_mismatchBackground = mismatchBackground;
	}

	/**
	 * Gets the background color when the searching text doesn't match with any
	 * of the elements in the component.
	 *
	 * @return the foreground color for mismatch. If you never call
	 *         {@link #setMismatchForeground(java.awt.Color)}. red color will be
	 *         used.
	 */
	public Color getMismatchBackground() {
		if (_mismatchBackground == null) {
			return DEFAULT_MISMATCH_BACKGROUND;
		} else {
			return _mismatchBackground;
		}
	}

	private Installer _installer;

	/**
	 * 获取搜索历史.
	 *
	 * @return 搜索历史.
	 * @since 3.4.1
	 */
	public String[] getSearchHistory() {
		return _searchHistory == null ? new String[0] : _searchHistory.toArray(new String[_searchHistory.size()]);
	}

	/**
	 * 设置搜索历史.
	 *
	 * @param searchHistory
	 *            搜索历史
	 * @since 3.4.1
	 */
	public void setSearchHistory(String[] searchHistory) {
		if (searchHistory == null || searchHistory.length == 0) {
			_searchHistory = null;
		} else {
			_searchHistory = new ArrayList<String>();
			_searchHistory.addAll(Arrays.asList(searchHistory));
		}
		DefaultComboBoxModel model = new DefaultComboBoxModel();
		if (_searchHistory != null) {
			for (int i = _searchHistory.size() - 1; i >= 0; i--) {
				model.addElement(_searchHistory.get(i));
			}
		}
		model.insertElementAt("", 0);
		_comboBox.setModel(model);
		_comboBox.setSelectedIndex(0);
	}

	/**
	 * Gets the maximum search history length.
	 *
	 * @return the maximum search history length.
	 * @see {@link #setMaxHistoryLength(int)}
	 * @since 3.4.1
	 */
	public int getMaxHistoryLength() {
		return _maxHistoryLength;
	}

	/**
	 * Sets the maximum search history length.
	 * <p/>
	 * By default, it's 0, which means there is no history to shown to keep the
	 * behavior backward compatibility. To show history with a JComboBox, please
	 * use this method to set a positive or negative value. Any negative value
	 * means that the history size is unlimited.
	 *
	 * @param maxHistoryLength
	 *            the maximum history length
	 * @since 3.4.1
	 */
	public void setMaxHistoryLength(int maxHistoryLength) {
		if (_maxHistoryLength != maxHistoryLength) {
			int old = _maxHistoryLength;
			_maxHistoryLength = maxHistoryLength;
			if (getMaxHistoryLength() == 0) {
				_leadingLabel.setLabelFor(_textField);
				_textField.setVisible(true);
				Object item = _comboBox.getEditor().getItem();
				_textField.setText(item == null ? "" : item.toString());
				_comboBox.setVisible(false);
			} else if (!_comboBox.isVisible()) {
				_leadingLabel.setLabelFor(_comboBox);
				_comboBox.setVisible(true);
				_comboBox.getEditor().setItem(_textField.getText());
				_textField.setVisible(false);
			}
			firePropertyChange(PROPERTY_MAX_HISTORY_LENGTH, old, _maxHistoryLength);
		}
	}

	/**
	 * Gets the flag indicating if the match count should be displayed in the
	 * status label.
	 *
	 * @return true if the match count should be displayed. Otherwise false.
	 * @see #setShowMatchCount(boolean)
	 * @since 3.5.2
	 */
	public boolean isShowMatchCount() {
		return _showMatchCount;
	}

	/**
	 * Sets the flag indicating if the match count should be displayed in the
	 * status label.
	 * <p/>
	 * By default, the flag is set to false to keep the original behavior.
	 *
	 * @param showMatchCount
	 * @since 3.5.2
	 */
	public void setShowMatchCount(boolean showMatchCount) {
		_showMatchCount = showMatchCount;
		if (getSearchable() != null) {
			getSearchable().setCountMatch(isShowMatchCount());
		}
	}

	/**
	 * The installer for SearchableBar.
	 */
	public interface Installer {
		/**
		 * Called to show the SearchableBar so that user can see it.
		 * <p/>
		 * For example, if you want to add a SearchableBar to the south of a
		 * JTextArea, you should add JTextArea to the CENTER of a BorderLayout
		 * panel. In this method, you add the SearchableBar to the SOUTH of the
		 * same BorderLayout panel.
		 *
		 * @param searchableBar
		 *            the searchable bar
		 */
		public void openSearchBar(SearchableBar searchableBar);

		/**
		 * Called to hide the SearchableBar.
		 *
		 * @param searchableBar
		 *            the searchable bar
		 */
		public void closeSearchBar(SearchableBar searchableBar);
	}

	public Installer getInstaller() {
		return _installer;
	}

	/**
	 * Sets the installer. Installer is responsible for the installation and
	 * uninstallation of SearchableBar.
	 *
	 * @param installer
	 *            the installer
	 */
	public void setInstaller(Installer installer) {
		_installer = installer;
	}

	/**
	 * Installs a SearchableBar on a component. This is just a convenient method
	 * for you, you can install it in your own code. See below for the actual
	 * code we used in this method.
	 * <p/>
	 * <code><pre>
	 * final SearchableBar searchableBar = new SearchableBar(searchable);
	 * searchableBar.setInstaller(installer);
	 * ((JComponent) searchable.getComponent()).registerKeyboardAction(new AbstractAction() {
	 *     public void actionPerformed(ActionEvent e) {
	 *         searchableBar.getInstaller().openSearchBar(searchableBar);
	 *         searchableBar.focusSearchField();
	 *     }
	 * }, keyStroke, JComponent.WHEN_FOCUSED);
	 * return searchableBar;
	 * </pre></code>
	 *
	 * @param searchable
	 *            the searchable
	 * @param keyStroke
	 *            the key stroke
	 * @param installer
	 *            the installer
	 *
	 * @return the SearchableBar that is created.
	 */
	public static SearchableBar install(Searchable searchable, KeyStroke keyStroke, Installer installer) {
		final SearchableBar searchableBar = new SearchableBar(searchable);
		searchableBar.setInstaller(installer);
		((JComponent) searchable.getComponent()).registerKeyboardAction(new AbstractAction() {
			private static final long serialVersionUID = 8328919754409621715L;

			public void actionPerformed(ActionEvent e) {
				searchableBar.getInstaller().openSearchBar(searchableBar);
				searchableBar.focusSearchField();
			}
		}, keyStroke, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
		return searchableBar;
	}

	@Override
	public void processKeyEvent(KeyEvent e) {
	}

	public int getVisibleButtons() {
		return _visibleButtons;
	}

	/**
	 * Sets visible buttons on <code>SearchableBar</code>.
	 *
	 * @param visibleButtons
	 *            bit-wise all of several constants. Valid constants are
	 *            <ul>
	 *            <li>{@link #SHOW_CLOSE} - the close button
	 *            <li>{@link #SHOW_NAVIGATION} - the find next and find previous
	 *            buttons
	 *            <li>{@link #SHOW_HIGHLIGHTS} - highlights all button
	 *            <li>{@link #SHOW_MATCHCASE} - match case button
	 *            <li>{@link #SHOW_WHOLE_WORDS} - word only button
	 *            <li>{@link #SHOW_REPEATS} - repeats button
	 *            <li>{@link #SHOW_STATUS} - status area
	 *            <li>{@link #SHOW_ALL} - all buttons
	 *            </ul>
	 *            For example, if you want to show only close and highlights all
	 *            button, call
	 *            <code>setVisibleButtons(SearchableBar.SHOW_CLOSE | SearchableBar.SHOW_HIGHLIGHTS)</code>
	 *            .
	 */
	public void setVisibleButtons(int visibleButtons) {
		_visibleButtons = visibleButtons;
		removeAll();
		installComponents();
		revalidate();
		repaint();
	}

	/**
	 * Checks if <code>SearchableBar</code> is in compact mode.
	 *
	 * @return true if in compact. Otherwise, false.
	 */
	public boolean isCompact() {
		return _compact;
	}

	/**
	 * Sets the <code>SearchableBar</code> to compact or full mode. In compact
	 * mode will only use icon for buttons v.s. full mode will use both icon and
	 * text for buttons.
	 *
	 * @param compact
	 *            the flag
	 */
	public void setCompact(boolean compact) {
		_compact = compact;
		_findNextButton.setText(_compact ? "" : getResourceString("SearchableBar.findNext"));
		_highlightsButton.setText(_compact ? "" : getResourceString("SearchableBar.highlights"));
		_findPrevButton.setText(_compact ? "" : getResourceString("SearchableBar.findPrevious"));
	}

	/**
	 * Gets the icons from SearchableBarIconsFactory. Subclass can override this
	 * method if they want to provide their own icon.
	 *
	 * @param name
	 *            the icon name
	 *
	 * @return the icon of the specified name.
	 */
	protected ImageIcon getImageIcon(String name) {
		return SearchableBarIconsFactory.getImageIcon(name);
	}

	/**
	 * Gets the localized string from resource bundle. Subclass can override it
	 * to provide its own string. Available keys are defined in swing.properties
	 * that begin with "SearchableBar.".
	 *
	 * @param key
	 *            the resource key
	 *
	 * @return the localized string.
	 */
	protected String getResourceString(String key) {
		return Resource.getResourceBundle(Locale.getDefault()).getString(key);
	}

	private void showMessage(String message) {
		hideMessage();

		_messagePopup = com.jidesoft.popup.JidePopupFactory.getSharedInstance().createPopup();
		JLabel label = new JLabel(message);
		label.setOpaque(true);
		label.setFont(UIDefaultsLookup.getFont("Label.font").deriveFont(Font.BOLD, 11));
		label.setBackground(new Color(253, 254, 226));
		label.setBorder(BorderFactory.createEmptyBorder(2, 6, 2, 6));
		label.setForeground(UIDefaultsLookup.getColor("ToolTip.foreground"));

		_messagePopup.getContentPane().setLayout(new BorderLayout());
		_messagePopup.getContentPane().add(label);
		if (_textField != null && _textField.isVisible()) {
			_messagePopup.setOwner(_textField);
		}
		if (_comboBox != null && _comboBox.isVisible()) {
			_messagePopup.setOwner(_comboBox);
		}

		_messagePopup.setDefaultMoveOperation(JidePopup.HIDE_ON_MOVED);
		_messagePopup.setTransient(true);
		_messagePopup.showPopup();

		addMouseMotionListener(_mouseMotionListener);
		if (_textField != null && _textField.isVisible()) {
			_textField.addKeyListener(_keyListener);
		}
		if (_comboBox != null && _comboBox.isVisible()) {
			_comboBox.addKeyListener(_keyListener);
		}
	}

	private void hideMessage() {
		if (_messagePopup != null) {
			_messagePopup.hidePopupImmediately();
			_messagePopup = null;
		}
		if (_mouseMotionListener != null) {
			removeMouseMotionListener(_mouseMotionListener);
		}
		if (_keyListener != null) {
			_textField.removeKeyListener(_keyListener);
			_comboBox.removeKeyListener(_keyListener);
		}
	}

	private void addSearchingTextToHistory(String searchingText) {
		if (searchingText == null || searchingText.length() == 0) {
			return;
		}
		if (_searchHistory == null) {
			_searchHistory = new ArrayList<String>();
		}
		if (_searchHistory.size() <= 0) {
			_searchHistory.add(searchingText);
			DefaultComboBoxModel model = new DefaultComboBoxModel();
			model.addElement(searchingText);
			_comboBox.setModel(model);
			return;
		}
		if (JideSwingUtilities.equals(_searchHistory.get(_searchHistory.size() - 1), searchingText)) {
			return;
		}
		_searchHistory.remove(searchingText); // remove existing entry first
		_searchHistory.add(searchingText);
		if (getMaxHistoryLength() > 0 && _searchHistory.size() > getMaxHistoryLength()) {
			_searchHistory.remove(0);
		}
		DefaultComboBoxModel model = new DefaultComboBoxModel();
		for (int i = _searchHistory.size() - 1; i >= 0; i--) {
			model.addElement(_searchHistory.get(i));
		}
		_comboBox.setModel(model);
	}
}
